package com.ar4j.sql;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;

import com.ar4j.type.ITypeConverter;

/**
 * An extension to Spring's MapSqlParameterSource, allowing for custom type conversions for parameters
 */
public class ExtendedMapSqlParameterSource extends MapSqlParameterSource {
  private Map<String, ITypeConverter<?, ?>> propertyTypeConvertorMap;

  /**
   * Construct a new parameter source with the given value map and type converter map
   */
  public ExtendedMapSqlParameterSource(Map<String, ?> values, Map<String, ITypeConverter<?, ?>> propertyTypeConvertorMap) {
    super(values);
    this.propertyTypeConvertorMap = propertyTypeConvertorMap;
  }

  /**
   * Construct a new parameter source with a single parameter name and value and a map of type converters
   */
  public ExtendedMapSqlParameterSource(String paramName, Object value, Map<String, ITypeConverter<?, ?>> propertyTypeConvertorMap) {
    super(paramName, value);
    this.propertyTypeConvertorMap = propertyTypeConvertorMap;
  }
  
  /**
   * @return the sql type of the associated converter if there is a specified type conversion for the given field, or the
   *         sql type provided by the parent class if no conversion is needed
   */
  @Override
  public int getSqlType(String paramName) {
    ITypeConverter<?, ?> converter = propertyTypeConvertorMap.get(paramName);
    if (converter != null) {
      return converter.getSqlType();
    } else {
      return super.getSqlType(paramName);
    }
  }

  /**
   * @return the value of a parameter, applying type conversions as needed
   */
  @SuppressWarnings("unchecked")
  @Override
  public Object getValue(String paramName) {
    Object value = super.getValue(paramName);
    ITypeConverter<Object, Object> converter = (ITypeConverter<Object, Object>) propertyTypeConvertorMap.get(paramName);
    if (converter != null) {
      value = converter.convertToStorage(value);
    }
    return value;
  }

  /**
   * @return a map of parameter values. Since values are retrieved by calling getValue, type
   * conversions are applied as needed
   */
  @SuppressWarnings("unchecked")
  @Override
  public Map getValues() {
    Map<String, Object> out =  new HashMap<String, Object>();
    for(String key : (Set<String>) super.getValues().keySet()) {
      out.put(key, getValue(key));
    }
    
    return out;
  }
  
  @Override
  public String toString() {
    return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
  }
}
